package repository

import (
	"context"
	"errors"
	"happy-book/internal/domain"
	"happy-book/internal/repository/cache"
	"happy-book/internal/repository/dao"
	"log"
)

// 异常定义
var (
	ErrDuplicatedEmail = dao.ErrDuplicatedEmail //使用Type Alias类型别名机制, 实现异常传导
	ErrUserNotFound    = dao.ErrRecordNotFound
)

// UserRepository 用户领域存储操作
type UserRepository struct {
	dao   *dao.UserDAO
	cache *cache.UserCache
}

// NewUserRepo 构造函数
// 通过依赖注入UserDAO
func NewUserRepo(dao *dao.UserDAO, cache *cache.UserCache) *UserRepository {
	return &UserRepository{
		dao:   dao,
		cache: cache,
	}
}

// Create 创建用户
func (repo *UserRepository) Create(ctx context.Context, user *domain.User) error {
	u := repo.domainUser2Dao(user)
	return repo.dao.Insert(ctx, u)
}

// FindByEmail 根据邮箱查询用户
func (repo *UserRepository) FindByEmail(ctx context.Context, email string) (*domain.User, error) {
	user, err := repo.dao.SelectByEmail(ctx, email)
	if err != nil {
		return &domain.User{}, err
	}
	u := repo.daoUser2Domain(user)
	return u, nil
}

// FindById 根据ID查询用户
func (repo *UserRepository) FindById(ctx context.Context, uid int64) (*domain.User, error) {
	// 先从缓存中查询,如果缓存hit则直接返回
	userDomain, err := repo.cache.Get(ctx, uid)
	if err == nil {
		return userDomain, nil
	}

	if errors.Is(err, cache.ErrKeyNotExists) { //缓存数据不存在, 从数据库中查询
		// 缓存miss, 从数据库中查询
		user, errr := repo.dao.SelectById(ctx, uid)
		if errr != nil {
			return &domain.User{}, errr
		}
		u := repo.daoUser2Domain(user)

		// 异步将数据库中的数据写入缓存
		go func() {
			err = repo.cache.Set(context.Background(), u)
			if err != nil { // 写入缓存失败,记录异常日志
				log.Printf("[UserRepository.FindById] cache.Set error: %v", err)
			}
		}()
		return u, nil
	}

	// 出现其他异常,说明Redis的可用性出了问题,进行降级处理,并打印日志,防止大量请求同时穿透到数据库
	log.Printf("[UserRepository.FindById] cache.Get error: %v", err)
	return nil, err
}

// Update 修改用户信息
func (repo *UserRepository) Update(ctx context.Context, user *domain.User) error {
	u := repo.domainUser2Dao(user)
	return repo.dao.UpdateNonZeroField(ctx, u)
}

// dao.user -> domain.user
func (repo *UserRepository) daoUser2Domain(user *dao.User) *domain.User {
	return &domain.User{
		Id:       user.Id,
		Email:    user.Email,
		Password: user.Password,
		Nickname: user.Nickname,
		Birthday: user.Birthday,
		AboutMe:  user.AboutMe,
		Avatar:   user.Avatar,
	}
}

// domain.user -> dao.user
func (repo *UserRepository) domainUser2Dao(user *domain.User) *dao.User {
	u := &dao.User{
		Email:    user.Email,
		Password: user.Password,
		Nickname: user.Nickname,
		Birthday: user.Birthday,
		AboutMe:  user.AboutMe,
		Avatar:   user.Avatar,
	}
	if user.Id > 0 {
		u.Id = user.Id
	}
	return u
}
